﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace Atlepage.Syntactic
{
    [Serializable]
    public class Parser
    {
        internal struct StackState
        {
            public readonly int State;
            public readonly object Value;
            public StackState(int state, object value)
            {
                State = state;
                Value = value;
            }
        }

        [Serializable]
        internal abstract class Action
        {
            public abstract bool Run(Stack<StackState> states, IEnumerator<TokenBase> it, Parser parser);

            public abstract string Conflict(Reduce reduce);
        }

        [Serializable]
        internal class Shift : Action
        {
            int newState;
            [NonSerialized]
            internal LRItemSet set;
            public Shift(int state, LRItemSet set)
            {
                newState = state;
                this.set = set;
            }

            public override bool Run(Stack<StackState> states, IEnumerator<TokenBase> it, Parser parser)
            {
                states.Push(new StackState(newState, it.Current));
                it.MoveNext();
                return false;
            }

            public override string Conflict(Reduce reduce)
            {
                return string.Format("SR Conflict: {0} and {1}", this.set, reduce.production);
            }

            public override string ToString()
            {
                return "s" + newState;
            }
        }

        [Serializable]
        internal class Reduce : Action
        {
            int nonTerm;
            object[] args;
            MethodInfo method;
            int productionId;
            /// <summary>
            /// This is used to print a slightly more useful error when
            /// there is a conflict.
            /// </summary>
            [NonSerialized]
            internal Production production;
            public Reduce(List<NonTerminal> nonTerms, Production production, int productionId)
            {
                nonTerm = nonTerms.IndexOf(production.Name);
                method = production.Method;
                args = new object[production.Nodes.Length];
                this.production = production;
                this.productionId = productionId;
            }

            public override bool Run(Stack<StackState> states, IEnumerator<TokenBase> it, Parser parser)
            {
                // build args
                for (int i = args.Length - 1; i >= 0; i--)
                    args[i] = states.Pop().Value;
                // build new state
                int newState = parser.gotoTable[states.Peek().State, nonTerm];
                object newValue = method.Invoke(parser.actor, args);
                states.Push(new StackState(newState, newValue));
                return false;
            }

            public override string Conflict(Reduce reduce)
            {
                return string.Format("RR Conflict: {0} and {1}", this.production, reduce.production);
            }

            public override string ToString()
            {
                return "r" + productionId;
            }
        }

        [Serializable]
        internal class Accept : Action
        {
            public override bool Run(Stack<StackState> states, IEnumerator<TokenBase> it, Parser parser)
            {
                return true;
            }

            public override string Conflict(Reduce reduce)
            {
                throw new NotImplementedException();
            }

            public override string ToString()
            {
                return "acc";
            }
        }

        Action[,] srTable;
        int[,] gotoTable;
        [NonSerialized]
        object actor;

        internal Parser(Action[,] srTable, int[,] gotoTable)
        {
            this.srTable = srTable;
            this.gotoTable = gotoTable;
        }

        public void Initialize(object actor)
        {
            this.actor = actor;
        }

        public object Parse(IEnumerable<TokenBase> tokens)
        {
            Stack<StackState> states = new Stack<StackState>();
            states.Push(new StackState(0, null));

            IEnumerator<TokenBase> it = tokens.GetEnumerator();
            it.MoveNext();

            while (true)
            {
                Action action = srTable[states.Peek().State, it.Current.Type];

                if (action == null)
                {
                    Type t = this.actor.GetType();
                    MethodInfo m = t.GetMethod("p_error");
                    if (m != null)
                        m.Invoke(this.actor, new object[0]);
                    else
                        Console.Error.WriteLine("Unexpected token {0}", it.Current);
                    return null;
                }

                if (action.Run(states, it, this))
                    break;
            }

            return states.Peek().Value;
        }

        internal Parser Clone()
        {
            return new Parser(srTable, gotoTable);
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < srTable.GetLength(0); i++)
            {
                for (int j = 0; j < srTable.GetLength(1); j++)
                {
                    Action a = srTable[i, j];
                    if (a == null)
                        sb.Append("    ");
                    else
                    {
                        string s = a.ToString();
                        sb.Append(' ', 4 - s.Length);
                        sb.Append(s);
                    }
                }
                for (int j = 0; j < gotoTable.GetLength(1); j++)
                {
                    int v = gotoTable[i, j];
                    if (v == -1)
                        sb.Append("    ");
                    else
                        sb.AppendFormat(" {0:000}", v);
                }
                sb.AppendLine();
            }

            return sb.ToString();
        }
    }
}
